package com.linx.web.filter;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.linx.common.core.utils.ServletUtils;
import com.linx.common.core.utils.StringUtils;
import com.linx.common.log.constant.LogType;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Configuration
@WebFilter(filterName = "logFilter", urlPatterns = "/*")
@Order(value = Integer.MIN_VALUE)
public class LogFilter implements Filter {

    // 响应数据截取长度
    private static final int RESPONSE_LENGTH = 500;
    private Logger log = LoggerFactory.getLogger(LogFilter.class);

    @Override
    public void init(FilterConfig filterConfig) {
        log.debug("过滤器-日志-初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        long requestTime = System.currentTimeMillis();
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String url = uri.substring(contextPath.length());

        // 顺丰接口日志
        if (url.startsWith("/open/sf-info")) {
            log = LoggerFactory.getLogger(LogType.SHUNFENG);
        } else {
            log = LoggerFactory.getLogger(LogFilter.class);
        }

        //  日志追踪
        MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));

        //  静态资源 跳过
        if (url.contains(".")) {
            filterChain.doFilter(request, response);
            return;
        }

        //  请求参数
        String requestData = "";
        String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE);

        //  普通表单提交
        if ("GET".equals(request.getMethod()) || requestContentType == null || requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            requestData = toJson(request.getParameterMap());
        }
        //  xml json
        else if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE) || requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)) {
            requestData = getRequestBody(request);
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestData.getBytes(StandardCharsets.UTF_8));
            request = new BufferedRequestWrapper(request) {
                @Override
                public ServletInputStream getInputStream() {
                    return new ByteArrayServletInputStream(byteArrayInputStream);
                }
            };
        }
        //  文件表单提交
        else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
            requestData = getFormParam(request);
        }

        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        response = new HttpServletResponseWrapper(response) {
            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                return new TeeServletOutputStream(super.getOutputStream(), byteArrayOutputStream);
            }
        };

        //  去掉字符串中的换行符、回车、制表符，将连续多个空格替换成一个空格
        requestData = Pattern.compile("[\t\n\r]").matcher(requestData).replaceAll("").replaceAll(" +", " ");
        String params = requestData.length() > 1000 ? requestData.substring(0, 1000) + "...【由于请求参数过长，只打印1000字符】" : requestData;
        log.info("[【请求地址】：{}] [【参数】：{}] [【请求方法】：{}] [【媒体类型】：{}] [【请求IP】：{}] [【{}】：{}]", url, params, request.getMethod(), requestContentType, getIpAddress(request), StpUtil.getTokenName(), request.getHeader(StpUtil.getTokenName()));

        //  执行下一个过滤器
        filterChain.doFilter(request, response);

        //  打印请求日志
        long costTime = System.currentTimeMillis() - requestTime;
        String responseBody = "";

        //  暂定只有json 输出响应体
        String responseContentType = response.getContentType();
        if (responseContentType != null && responseContentType.startsWith(MediaType.APPLICATION_JSON_VALUE)) {
            responseBody = byteArrayOutputStream.toString();
        }

        //  去掉字符串中的换行符、回车、制表符，将连续多个空格替换成一个空格
        responseBody = Pattern.compile("[\t\n\r]").matcher(responseBody).replaceAll("").replaceAll(" +", " ");

        //  响应数据截取，防止控制台输出太多没有信息
        log.info("[【响应数据】：{}] [【耗时】：{}ms]", responseBody.length() > RESPONSE_LENGTH ? responseBody.substring(0, RESPONSE_LENGTH) + "...【由于响应数据过长，只响应" + RESPONSE_LENGTH + "字符】" : responseBody, costTime);

        MDC.remove("traceId");
    }

    @Override
    public void destroy() {
        log.debug("过滤器-日志-销毁");
    }

    public static class ByteArrayServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream byteArrayInputStream;

        public ByteArrayServletInputStream(ByteArrayInputStream byteArrayInputStream) {
            this.byteArrayInputStream = byteArrayInputStream;
        }

        @Override
        public int read() {
            return byteArrayInputStream.read();
        }

        @Override
        public boolean isFinished() {
            return byteArrayInputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            throw new UnsupportedOperationException();
        }
    }

    public static class BufferedRequestWrapper extends HttpServletRequestWrapper {

        private final byte[] buffer;

        public BufferedRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            InputStream is = request.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int len;
            while ((len = is.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }
            buffer = baos.toByteArray();
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new ByteArrayServletInputStream(new ByteArrayInputStream(buffer));
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buffer), getCharacterEncoding()));
        }
    }

    private String getRequestBody(HttpServletRequest request) {
        try {
            return IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("获取请求体失败", e);
            return "";
        }
    }

    private String getFormParam(HttpServletRequest request) {
        MultipartResolver resolver = new StandardServletMultipartResolver();
        MultipartHttpServletRequest mRequest = resolver.resolveMultipart(request);

        Map<String, Object> param = new HashMap<>();
        Map<String, String[]> parameterMap = mRequest.getParameterMap();
        if (!parameterMap.isEmpty()) {
            param.putAll(parameterMap);
        }
        Map<String, MultipartFile> fileMap = mRequest.getFileMap();
        if (!fileMap.isEmpty()) {
            for (Map.Entry<String, MultipartFile> fileEntry : fileMap.entrySet()) {
                MultipartFile file = fileEntry.getValue();
                param.put(fileEntry.getKey(), file.getOriginalFilename() + "(" + file.getSize() + " byte)");
            }
        }
        return toJson(param);
    }

    private static String toJson(Object object) {
        return JSON.toJSONStringWithDateFormat(object, "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 输出流
     */
    private static class TeeServletOutputStream extends ServletOutputStream {
        private final TeeOutputStream teeOutputStream;

        public TeeServletOutputStream(OutputStream one, OutputStream two) {
            this.teeOutputStream = new TeeOutputStream(one, two);
        }

        @Override
        public void write(int b) throws IOException {
            this.teeOutputStream.write(b);
        }

        @Override
        public void flush() throws IOException {
            super.flush();
            this.teeOutputStream.flush();
        }

        @Override
        public void close() throws IOException {
            super.close();
            this.teeOutputStream.close();
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
            // 如果不需要支持非阻塞写入，可以抛出异常
            throw new UnsupportedOperationException("Non-blocking I/O is not supported");
        }
    }

    /**
     * 获取用户真实IP地址
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
